#ifdef  MPI_ENDBG
#define DBG(...)		printf( __VA_ARGS__ )
#else
#define DBG(...)
#endif

#define	LEN(a, t)	sizeof(a)/sizeof(t)

/* getopt */
#include <unistd.h>
/* errno */
#include <errno.h>
/* clock */
#include <time.h>

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

#include <mpi.h>

#include "bitonic_interface_mpi.h"


#define DEFAULT_TEST_LEN		1024


void usage(int argc, char *argv[]) {
	printf(
"Usage %s [-f file_in | -s shm_in -l len] [-h] [-t]\n\
-l : size of the shared memory in data_t's\n\
-h : help\n\
-t : short self test\n\
", argv[0]);
}

size_t find_m(data_t data[], size_t l) {
	for(size_t i = 1; i < l; i++) {
		if(data[i-1] > data[i]) {
			return (i-1);
		}
	}
	return l-1;
}

void print_array(data_t *a, size_t l) {
	printf("\n");
	for(size_t i = 0; i < l; i++)
		printf("  %x  ", a[i]);
	printf("\n");
	for(size_t i = 0;i < l; i++)
		printf(" [%ld] ", i);
	printf("\n");
}

int is_valid_input(data_t *in, size_t l) {
	size_t i = 1;

	for(; i < l; i++) {
		if(in[i-1] > in[i]) {
			break;
		}
	}
	i++;
	for(; i < l; i++) {
		if(in[i-1] < in[i]) {
			return 0;
		}
	}
	return 1;
}

int is_sorted(data_t *in, size_t l) {
	for(size_t i = 1; i < l; i++) {
		if(in[i-1] > in[i]) {
			return 0;
		}
	}
	return 1;
}

void run_test(data_t *in, size_t l, int r_total) {
	//size_t m = find_m(in, l);
	printf("Testing:\n");
	print_array(in, l);
	bitonic_merge_master(in, l, 0, r_total);

	if(!is_sorted(in, l)) {
		print_array(in, l);
		printf("Failed.\n");
	} else {
		printf("Passed.\n");
	}
}

void self_test(int r_total) {
	/*data_t test1[8] = {1, 2, 3, 4, 2, 3, 4, 5};
	data_t test2[8] = {1, 2, 1, 2, 3, 4, 5, 6};
	data_t test3[8] = {1, 2, 3, 4, 5, 6, 1, 2};
	data_t test4[8] = {1, 2, 3, 4, 5, 6, 7, 8};
	data_t test5[9] = {1, 2, 3, 4, 5, 6, 2, 3, 4};
	data_t test6[9] = {1, 2, 3, 4, 5, 1, 2, 3, 4};
	data_t test7[9] = {1, 2, 3, 1, 2, 3, 4, 5, 6};
	*/
	data_t test1[8] = {1, 2, 3, 4, 5, 4, 3, 2};
	data_t test2[5] = {7, 8, 9, 3, 2};
	data_t test3[8] = {6, 7, 8, 9, 3, 2, 1, 0};
	data_t test4[9] = {1, 2, 3, 4, 9, 8, 7, 6, 5};
	data_t test5[9] = {5, 6, 7, 8, 9, 4, 3, 2, 1};
	
	printf("Starting self Tests ..\n");
	run_test(test1, LEN(test1, data_t), r_total);
	run_test(test2, LEN(test2, data_t), r_total);
	run_test(test3, LEN(test3, data_t), r_total);
	run_test(test4, LEN(test4, data_t), r_total);
	run_test(test5, LEN(test5, data_t), r_total);
	/*run_test(test6, LEN(test6, data_), r_totalt);
	run_test(test7, LEN(test7, data_t), r_totalt);*/
	printf("Finished self Tests ..\n");
}

void create_input(data_t *in, size_t l) {
	size_t i = 0;

	for(; i < l/2; i++) {
		in[i] = i;
		in[l-1-i] = i;
	}

	if(l%2 == 1) {
		in[(l/2)] = i+1;
	}
}

void mpi_main(int argc, char *argv[], int r, int r_total) {
	int opt;
	long l_data_t;
	data_t *data_in;
	extern char *optarg;

	l_data_t = 0;

	printf("r: %d r_total: %d\n", r, r_total);

	while((opt = getopt(argc, argv, "l:t")) != -1) {
		switch(opt) {
			case 't' : {
				self_test(r_total);
				break;
			}
			case 'l' : {
				l_data_t = strtol(optarg, NULL, 10);
				break;
			}
			case 'h':
			default : {
				usage(argc, argv);
			}
		}
	}

	if(l_data_t == 0) {
		l_data_t = DEFAULT_TEST_LEN;
	}

	data_in = malloc(sizeof(data_t)*l_data_t);

	create_input(data_in, l_data_t);

	if(is_valid_input(data_in, l_data_t)) {
		if(is_sorted(data_in, l_data_t)) {
			printf("Input sequence is already sorted!\n");
			return;
		}
		//size_t m = find_m(data_in, l_data_t);

		struct timespec cpu_time;
		struct timespec real_time;

		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time);
		clock_gettime(CLOCK_REALTIME, &real_time);

		time_t start_sec_cpu = cpu_time.tv_sec;
		time_t start_nsec_cpu = cpu_time.tv_nsec;
		time_t start_sec_real = real_time.tv_sec;
		time_t start_nsec_real = real_time.tv_nsec;

		bitonic_merge_master(data_in, l_data_t, 0, r_total);

		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_time);
		clock_gettime(CLOCK_REALTIME, &real_time);

		time_t cpu_nsec = cpu_time.tv_nsec-start_nsec_cpu;
		time_t real_nsec = real_time.tv_nsec-start_nsec_real;

		time_t cpu_sec = cpu_time.tv_sec-start_sec_cpu;
		time_t real_sec = real_time.tv_sec-start_sec_real;

		if(start_nsec_cpu > cpu_time.tv_nsec) {
			cpu_sec--;
			cpu_nsec = 1000000000 - start_nsec_cpu + cpu_time.tv_nsec;
		}

		if(start_nsec_real > real_time.tv_nsec) {
			real_sec--;
			real_nsec = 1000000000 - start_nsec_real + real_time.tv_nsec;
		}

		/* TODO fix this */
		printf(" cpu  %ld.%09ld\n", cpu_sec, cpu_nsec);
		printf(" real %ld.%09lu\n", real_sec, real_nsec);

		printf(" %d\n", is_sorted(data_in, l_data_t));
	} else {
		printf("Invalid input.\n");
	}
}

int main(int argc, char *argv[]) {
	int r;
	int r_total;


	MPI_Init(&argc, &argv);

	MPI_Comm_rank(MPI_COMM_WORLD, &r);
	MPI_Comm_size(MPI_COMM_WORLD, &r_total);

	if(r == 0) {
		if(!((r_total > 0) && !(r_total & (r_total - 1)))) {
			printf("p is required to be a power of 2 (%d)\n", r_total);
		} else {
			mpi_main(argc, argv, r, r_total);
		}
	} else {
		if((r_total > 0) && !(r_total & (r_total - 1))) {
			bitonic_merge_child(r, r_total);
		}
	}

	MPI_Finalize();
	return 0;
}
